La estructura básica
Toda aplicación que creemos en shiny consta de al menos:
Una interfaz gráfica, UI.
Una función de servidor, SERVER.
La introducción de una función para correr la app, shinyApp().
library(shiny)
# Define UI for application
ui <- fluidPage(
)
# Define server logic required
server <- function(input, output) {
}
# Run the application
shinyApp(ui = ui, server = server)
Dentro de cualquier shiny app se puede dar clic al botón “Run app”
Personalización de la interfaz gráfica (ui)
Colocación de elementos dentro de la página
Existen muchos tipos de formatos que se pueden utilizar y todas estas formas de estructurar las páginas se pueden combinar unas con otras en un sinfín de posibilidades.
Filas de objetos, fluidRow
Si se desean acomodar objetos por filas, se puede utilizar el formato fluidRow(), donde se especifican columnas con column(), y lo que cada una contendrá.
El ancho de cada columna se especifica con un número del 1 al 12. Preferentemente, la suma de todas las columnas debería ser 12.
ui <- fluidPage(
# Panel de título
titlePanel("Título de la app"),
# Primera fila
fluidRow(
column(width = 12,
"objeto de la columna única")
),
# Segunda fila
fluidRow(
column(width = 2,
"objetos de la col. 1"),
column(width = 5,
"objetos de la col. 2"),
column(width = 5,
"objetos de la col. 3")
)
)
Diseño de objetos fluidos, flowLayout
Si se quieren acomodar varios objetos de forma consecutiva (de izquierda a derecha) y que éstos vayan tomando distintas filas, dependiendo del tamaño de la ventana, se puede utilizar flowLayout.
ui <- fluidPage(
# Panel de título
titlePanel("Título de la app"),
flowLayout(
"objeto 1",
"objeto 2",
"objeto 3",
"etc."
)
)
Objetos horizontalmente, splitLayout
Esta estructura asigna el mismo tamaño a todos los objetos (al menos de manera predeterminada), y los acomoda de manera horizontal. Aquí no son fluidos. Su tamaño cambiará para ajustarse al tamaño de la ventana, pero siempre mantendrán su acomodo.
ui <- fluidPage(
# Panel de título
titlePanel("Título de la app"),
splitLayout(
"objeto 1",
"objeto 2",
"objeto 3",
"etc."
)
)

Objetos verticalmente, verticalLayout
Si se desean acomodar objetos acomodados de manera vertical entre sí, se puede utilizar esta estructura.
ui <- fluidPage(
# Panel de título
titlePanel("Título de la app"),
verticalLayout(
"objeto 1",
"objeto 2",
"objeto 3",
"etc."
)
)
Agregar capas de objetos con tabPanels
Independientemente del tipo de presentación de las capas que se seleccione, cada capa debe ir especificada con tabPanel().
Panel con pestañas, tabsetPanel
Estos tabPanels pueden ser de dos tipos: pestañas tabs (defáult) y píldoras pills.
tabs
ui <- fluidPage(
# Panel de título
titlePanel("Título de la app"),
tabsetPanel(type = "tabs",
tabPanel("Panel 1",
...
),
tabPanel("Panel 2",
...
),
tabPanel("Panel 3",
...
)
)
)

pills
El código es casi igual, solo cambia el argumento type = "pills".

Página con barra de navegación, navbarPage
Este formato crea una barra de navegación con botones para ls distintos tabPanels:
ui <- fluidPage(
# Panel de título
titlePanel("Título de la app"),
navbarPage("Título de la barra",
tabPanel("Panel 1",
...
),
tabPanel("Panel 2",
...
),
tabPanel("Panel 3",
...
)
)
)

Panel con lista de navegación, navlistPanel
Se puede crear una lista de navegación, que incluya varios paneles para mostrar distinta información.
ui <- fluidPage(
# Panel de título
titlePanel("Título de la app"),
navlistPanel(
tabPanel("Panel 1",
...
),
tabPanel("Panel 2",
...
),
tabPanel("Panel 3",
...
)
)
)

Funciones HTML
Las shiny apps, al igual que los R Notebooks o R Markdown funcionan a base de HTML, por lo que se pueden utilizar funciones sin mayor problema.
Algunas de estas son:
h#() para distintos niveles de encabezados.
p() para generar párrafos.
strong() para poner texto en negritas.
em() genera texto en cursiva.
code() pone texto en formato de código.
ui <- fluidPage(
titlePanel("Título de la app"),
sidebarLayout(
sidebarPanel("sidebar panel"),
mainPanel("main panel",
h1("encabezado nivel 1"),
h2("encabezado nivel 2"),
h3("encabezado nivel 3"),
p("Inicia párrafo con texto normal,",
strong("texto en negritas dentro del párrafo,"),
em("también texto en cursiva"), "y",
code("texto con formato de código")),
p("Un nuevo párrafo."))
)
)
Temas
La paquetería shinythemes cuenta con varios temas preinstalados que se pueden agregar fácilmente a la app. Para utilizarlos, basta con mandar llamar la librería y, dentro de la interfaz de usuario definir theme = shinytheme("tema escogido"), para que su app se muestre así. Pueden ver una lista de los temas soportados actuales aquí. Por ejemplo:
shinyUI(navbarPage(title = "Data analysis app",
theme = shinytheme("united"),
tabPanel("Seattle House prices",
)
)
)
Más personalización con HTML y CSS
Si están familiarizados con HTML y CSS, se puede personalizar cuanto quieran su aplicación o partes de ella.
P. ej., se puede agregar un archivo con todas las características CSS. Para esto, se necesita poner en una carpeta (dentro del directorio de la app) que lleve por nombre “www” y para cargar el tema, se haría de manera similar que con los shinythemes. Asumiendo que su archivo CSS se llama “bootstrap.css”, se podría cargar así:
shinyUI(navbarPage(title = "Data analysis app",
theme = "bootstrap.css",
tabPanel("Seattle House prices",
)
)
)
Carga de imágenes
Se pueden incluir imágenes dentro de la app, como logotipos, fondos, etc. Los archivos deben estar guardados también en la carpeta “wwww” para que R pueda encontrarlos.
La imagen se puede definir dentro de cualquier tipo de interfaz seleccionada con la función img().
img(src = "image.jpg",
height = "70%", width = "70%", align = "center")
Objetos en shiny
Hasta ahora solo hemos visto cómo se puede personalizar la app. Sin embargo, no hemos agregado ningún objeto con el que el usuario pueda interactuar (inputs) o visualizar después de su interacción (outputs).
Respuesta de la app, outputs
Para poder mostrar gráficas, tablas, texto, etc. una vez que el usuario interactuó con los inputs, es necesario definir (todavía dentro de la ui) el tipo de output que queremos mostrar.
Estas funciones de output van acompañadas de una función similar en la parte del servidor. Debajo se muestran varias funciones comunes, junto con su par para la parte del servidor.
plotOutput() |
Gráfica estática |
renderPlot() |
plotlyOutput() |
Gráfica interactiva |
renderPlotly() |
tableOutput() |
Tabla de datos |
renderTable() |
dataTableOutput() |
Tabla interactiva |
renderDataTable() |
textOutput() |
Texto con formato |
renderText() |
verbatimTextOutput() |
Texto de consola |
renderPrint() |
imageOutput() |
Imagen |
renderImage() |
Configuración del servidor, server
LS0tDQp0aXRsZTogIkxvcyBlbGVtZW50b3MgYsOhc2ljb3MgZGUgdW5hIFNoaW55IEFwcCINCmF1dGhvcjogIlBhYmxvIEJlbmF2aWRlcy1IZXJyZXJhIg0KZGF0ZTogMjAyMC0wNi0xMw0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICB0aGVtZTogdW5pdGVkDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGV2YWwgPSBGQUxTRSkNCmBgYA0KDQoNCiMgTGEgZXN0cnVjdHVyYSBiw6FzaWNhDQoNClRvZGEgYXBsaWNhY2nDs24gcXVlIGNyZWVtb3MgZW4gc2hpbnkgY29uc3RhIGRlIGFsIG1lbm9zOg0KDQoqIFVuYSBpbnRlcmZheiBncsOhZmljYSwgKipVSSoqLg0KDQoqIFVuYSBmdW5jacOzbiBkZSBzZXJ2aWRvciwgKipTRVJWRVIqKi4NCg0KKiBMYSBpbnRyb2R1Y2Npw7NuIGRlIHVuYSBmdW5jacOzbiBwYXJhIGNvcnJlciBsYSBhcHAsIGBzaGlueUFwcCgpYC4NCg0KDQpgYGB7ciBzaGlueS1zbmlwcGV0LCBldmFsID0gRkFMU0V9DQpsaWJyYXJ5KHNoaW55KQ0KDQojIERlZmluZSBVSSBmb3IgYXBwbGljYXRpb24NCnVpIDwtIGZsdWlkUGFnZSgNCg0KKQ0KDQojIERlZmluZSBzZXJ2ZXIgbG9naWMgcmVxdWlyZWQNCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0KSB7DQoNCg0KfQ0KDQojIFJ1biB0aGUgYXBwbGljYXRpb24gDQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpDQoNCmBgYA0KDQpEZW50cm8gZGUgY3VhbHF1aWVyICpzaGlueSBhcHAqIHNlIHB1ZWRlIGRhciBjbGljIGFsIGJvdMOzbiAqKiJSdW4gYXBwIioqDQoNCg0KIyBQZXJzb25hbGl6YWNpw7NuIGRlIGxhIGludGVyZmF6IGdyw6FmaWNhIChgdWlgKQ0KDQojIyBDb2xvY2FjacOzbiBkZSBlbGVtZW50b3MgZGVudHJvIGRlIGxhIHDDoWdpbmEgey50YWJzZXR9DQoNCkV4aXN0ZW4gbXVjaG9zIHRpcG9zIGRlIGZvcm1hdG9zIHF1ZSBzZSBwdWVkZW4gdXRpbGl6YXIgeSB0b2RhcyBlc3RhcyBmb3JtYXMgZGUgZXN0cnVjdHVyYXIgbGFzIHDDoWdpbmFzIHNlIHB1ZWRlbiBjb21iaW5hciB1bmFzIGNvbiBvdHJhcyBlbiB1biBzaW5mw61uIGRlIHBvc2liaWxpZGFkZXMuDQoNCiMjIyBEaXNlw7FvIGRlIGJhcnJhIGxhdGVyYWwsIGBzaWRlYmFyTGF5b3V0YA0KDQpMYSBmdW5jacOzbiBgc2lkZWJhckxheW91dChzaWRlYmFyUGFuZWwsIG1haW5QYW5lbCwgcG9zaXRpb24gPSBjKCJsZWZ0IiwgInJpZ2h0IikpYCBub3MgcGVybWl0ZSBjcmVhciB1bmEgYXBwIHF1ZSBjb250ZW5nYSB1bmEgYmFycmEgc2VjdW5kYXJpYSBwb3IgdW4gbGFkbyAoKippenEuKiogbyBkZXIuKSwgeSB1biBwYW5lbCBwcmluY2lwYWwuIFBhcmEgZXN0bywgZXNwZWNpZmljYW1vcyBkZW50cm8gZGUgZXN0YSBmdW5jacOzbiBsYXMgZnVuY2lvbmVzIGBzaWRlYmFyUGFuZWwoKWAgeSBgbWFpblBhbmVsKClgLCByZXNwZWN0aXZhbWVudGUuDQoNCkNhZGEgcGFuZWwgcHVlZGUgY29udGVuZXIgdW5vIG8gbcOhcyBvYmpldG9zLg0KDQpgYGB7ciBzaWRlYmFyTGF5b3V0fQ0KdWkgPC0gZmx1aWRQYWdlKA0KICAjIFBhbmVsIGRlIHTDrXR1bG8NCiAgdGl0bGVQYW5lbCgiVMOtdHVsbyBkZSBsYSBhcHAiKSwNCiAgDQogICMgRXNwZWNpZmljYWNpw7NuIGRlIGxhIHNlcGFyYWNpw7NuIGVudHJlIHBhbmVsIGxhdGVyYWwgeSBwcmluY2lwYWwNCiAgc2lkZWJhckxheW91dCgNCiAgICANCiAgICAjIFBhbmVsIGxhdGVyYWwNCiAgICBzaWRlYmFyUGFuZWwoIlTDrXR1bG8gZGVsIHBhbmVsIiksDQogICAgDQogICAgIyBQYW5lbCBwcmluY2lwYWwNCiAgICBtYWluUGFuZWwoIlTDrXR1bG8gZGVsIG1haW4gcGFuZWwiKQ0KICAgICkNCikNCmBgYA0KDQohW0Fjb21vZGFuZG8gbG9zIG9iamV0b3MgZW4gdW5hIGJhcnJhIGxhdGVyYWwgeSB1biBwYW5lbCBwcmluY2lwYWwgY29uIGBzaWRlYmFyTGF5b3V0YF0oZmlncy9zaWRlYmFyTGF5b3V0LnBuZykNCg0KIyMjIEZpbGFzIGRlIG9iamV0b3MsIGBmbHVpZFJvd2ANCg0KU2kgc2UgZGVzZWFuIGFjb21vZGFyIG9iamV0b3MgcG9yIGZpbGFzLCBzZSBwdWVkZSB1dGlsaXphciBlbCBmb3JtYXRvIGBmbHVpZFJvdygpYCwgZG9uZGUgc2UgZXNwZWNpZmljYW4gKipjb2x1bW5hcyoqIGNvbiBgY29sdW1uKClgLCB5IGxvIHF1ZSBjYWRhIHVuYSBjb250ZW5kcsOhLg0KDQpFbCBhbmNobyBkZSBjYWRhIGNvbHVtbmEgc2UgZXNwZWNpZmljYSBjb24gdW4gbsO6bWVybyBkZWwgKioxIGFsIDEyKiouIFByZWZlcmVudGVtZW50ZSwgbGEgc3VtYSBkZSB0b2RhcyBsYXMgY29sdW1uYXMgZGViZXLDrWEgc2VyICoqMTIqKi4NCg0KYGBge3IgZmx1aWRSb3d9DQp1aSA8LSBmbHVpZFBhZ2UoDQogICMgUGFuZWwgZGUgdMOtdHVsbw0KICB0aXRsZVBhbmVsKCJUw610dWxvIGRlIGxhIGFwcCIpLA0KICANCiAgIyBQcmltZXJhIGZpbGENCiAgZmx1aWRSb3coDQogICAgY29sdW1uKHdpZHRoID0gMTIsDQogICAgICAgICAgICJvYmpldG8gZGUgbGEgY29sdW1uYSDDum5pY2EiKQ0KICApLA0KICAjIFNlZ3VuZGEgZmlsYQ0KICBmbHVpZFJvdygNCiAgICBjb2x1bW4od2lkdGggPSAyLA0KICAgICAgICAgICAib2JqZXRvcyBkZSBsYSBjb2wuIDEiKSwNCiAgICBjb2x1bW4od2lkdGggPSA1LA0KICAgICAgICAgICAib2JqZXRvcyBkZSBsYSBjb2wuIDIiKSwNCiAgICBjb2x1bW4od2lkdGggPSA1LA0KICAgICAgICAgICAib2JqZXRvcyBkZSBsYSBjb2wuIDMiKQ0KICApDQopDQpgYGANCg0KIVtEb3MgZmlsYXMgaGVjaGFzIGNvbiBgZmx1aWRSb3dgXShmaWdzL2ZsdWlkUm93LlBORykNCg0KIyMjIERpc2XDsW8gZGUgb2JqZXRvcyBmbHVpZG9zLCBgZmxvd0xheW91dGANCg0KU2kgc2UgcXVpZXJlbiBhY29tb2RhciB2YXJpb3Mgb2JqZXRvcyBkZSBmb3JtYSBjb25zZWN1dGl2YSAoZGUgaXpxdWllcmRhIGEgZGVyZWNoYSkgeSBxdWUgw6lzdG9zIHZheWFuIHRvbWFuZG8gZGlzdGludGFzIGZpbGFzLCBkZXBlbmRpZW5kbyBkZWwgdGFtYcOxbyBkZSBsYSB2ZW50YW5hLCBzZSBwdWVkZSB1dGlsaXphciBgZmxvd0xheW91dGAuDQoNCmBgYHtyIGZsb3dMYXlvdXR9DQp1aSA8LSBmbHVpZFBhZ2UoDQogICMgUGFuZWwgZGUgdMOtdHVsbw0KICB0aXRsZVBhbmVsKCJUw610dWxvIGRlIGxhIGFwcCIpLA0KICANCiAgZmxvd0xheW91dCgNCiAgICAib2JqZXRvIDEiLA0KICAgIA0KICAgICJvYmpldG8gMiIsDQogICAgDQogICAgIm9iamV0byAzIiwNCiAgICANCiAgICAiZXRjLiINCiAgKQ0KKQ0KYGBgDQoNCiFbYGZsb3dMYXlvdXRgIGNvbiB1bmEgdmVudGFuYSBhbXBsaWFdKGZpZ3MvZmxvd0xheW91dF93aWRlLlBORykNCg0KIVtgZmxvd0xheW91dGAgY29uIHVuYSB2ZW50YW5hIGFuZ29zdGFdKGZpZ3MvZmxvd0xheW91dF9sb25nLlBORykNCg0KIyMjIE9iamV0b3MgaG9yaXpvbnRhbG1lbnRlLCBgc3BsaXRMYXlvdXRgDQoNCkVzdGEgZXN0cnVjdHVyYSBhc2lnbmEgZWwgbWlzbW8gdGFtYcOxbyBhIHRvZG9zIGxvcyBvYmpldG9zIChhbCBtZW5vcyBkZSBtYW5lcmEgcHJlZGV0ZXJtaW5hZGEpLCB5IGxvcyBhY29tb2RhIGRlIG1hbmVyYSBob3Jpem9udGFsLiBBcXXDrSBubyBzb24gZmx1aWRvcy4gU3UgdGFtYcOxbyBjYW1iaWFyw6EgcGFyYSBhanVzdGFyc2UgYWwgdGFtYcOxbyBkZSBsYSB2ZW50YW5hLCBwZXJvIHNpZW1wcmUgbWFudGVuZHLDoW4gc3UgYWNvbW9kby4NCg0KYGBge3Igc3BsaXRMYXlvdXR9DQp1aSA8LSBmbHVpZFBhZ2UoDQogICMgUGFuZWwgZGUgdMOtdHVsbw0KICB0aXRsZVBhbmVsKCJUw610dWxvIGRlIGxhIGFwcCIpLA0KICANCiAgc3BsaXRMYXlvdXQoDQogICAgIm9iamV0byAxIiwNCiAgICANCiAgICAib2JqZXRvIDIiLA0KICAgIA0KICAgICJvYmpldG8gMyIsDQogICAgDQogICAgImV0Yy4iDQogICkNCikNCmBgYA0KDQohW10oZmlncy9zcGxpdExheW91dC5QTkcpDQoNCiMjIyBPYmpldG9zIHZlcnRpY2FsbWVudGUsIGB2ZXJ0aWNhbExheW91dGANClNpIHNlIGRlc2VhbiBhY29tb2RhciBvYmpldG9zIGFjb21vZGFkb3MgZGUgbWFuZXJhIHZlcnRpY2FsIGVudHJlIHPDrSwgc2UgcHVlZGUgdXRpbGl6YXIgZXN0YSBlc3RydWN0dXJhLg0KDQpgYGB7ciB2ZXJpY2FsTGF5b3V0fQ0KdWkgPC0gZmx1aWRQYWdlKA0KICAjIFBhbmVsIGRlIHTDrXR1bG8NCiAgdGl0bGVQYW5lbCgiVMOtdHVsbyBkZSBsYSBhcHAiKSwNCiAgDQogIHZlcnRpY2FsTGF5b3V0KA0KICAgICJvYmpldG8gMSIsDQogICAgDQogICAgIm9iamV0byAyIiwNCiAgICANCiAgICAib2JqZXRvIDMiLA0KICAgIA0KICAgICJldGMuIg0KICApDQopDQpgYGANCg0KIVtgdmVydGljYWxMYXlvdXRgIGVuIGNvbmp1bnRvIGNvbiBgZmxvd0xheW91dGBdKGZpZ3MvdmVydGljYWxMYXlvdXQuUE5HKQ0KDQoNCg0KKioqDQoNCiMjIEFncmVnYXIgY2FwYXMgZGUgb2JqZXRvcyBjb24gYHRhYlBhbmVsc2Agey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KSW5kZXBlbmRpZW50ZW1lbnRlIGRlbCB0aXBvIGRlIHByZXNlbnRhY2nDs24gZGUgbGFzIGNhcGFzIHF1ZSBzZSBzZWxlY2Npb25lLCBjYWRhIGNhcGEgZGViZSBpciBlc3BlY2lmaWNhZGEgY29uIGB0YWJQYW5lbCgpYC4gDQoNCiMjIyBQYW5lbCBjb24gcGVzdGHDsWFzLCBgdGFic2V0UGFuZWxgDQoNCkVzdG9zIGB0YWJQYW5lbHNgIHB1ZWRlbiBzZXIgZGUgZG9zIHRpcG9zOiAqKnBlc3Rhw7FhcyBgdGFic2AqKiAoZGVmw6F1bHQpIHkgcMOtbGRvcmFzIGBwaWxsc2AuDQoNCiMjIyMgYHRhYnNgDQoNCmBgYHtyIHRhYnNldFBhbmVsLXRhYnN9DQp1aSA8LSBmbHVpZFBhZ2UoDQogICMgUGFuZWwgZGUgdMOtdHVsbw0KICB0aXRsZVBhbmVsKCJUw610dWxvIGRlIGxhIGFwcCIpLA0KICANCiAgdGFic2V0UGFuZWwodHlwZSA9ICJ0YWJzIiwNCiAgICANCiAgICB0YWJQYW5lbCgiUGFuZWwgMSIsDQogICAgICAgICAgICAgLi4uDQogICAgICAgICAgICAgKSwNCiAgICANCiAgICB0YWJQYW5lbCgiUGFuZWwgMiIsDQogICAgICAgICAgICAgLi4uDQogICAgICAgICAgICAgKSwNCiAgICANCiAgICB0YWJQYW5lbCgiUGFuZWwgMyIsDQogICAgICAgICAgICAgLi4uDQogICAgICAgICAgICAgKQ0KICApDQopDQpgYGANCg0KIVtdKGZpZ3MvdGFic2V0UGFuZWwtdGFicy5QTkcpDQoNCiMjIyMgYHBpbGxzYA0KDQpFbCBjw7NkaWdvIGVzIGNhc2kgaWd1YWwsIHNvbG8gY2FtYmlhIGVsIGFyZ3VtZW50byBgdHlwZSA9ICJwaWxscyJgLg0KDQoNCiFbXShmaWdzL3RhYnNldFBhbmVsLXBpbGxzLlBORykNCg0KDQojIyMgUMOhZ2luYSBjb24gYmFycmEgZGUgbmF2ZWdhY2nDs24sIGBuYXZiYXJQYWdlYA0KDQpFc3RlIGZvcm1hdG8gY3JlYSB1bmEgYmFycmEgZGUgbmF2ZWdhY2nDs24gY29uIGJvdG9uZXMgcGFyYSBscyBkaXN0aW50b3MgYHRhYlBhbmVsc2A6DQoNCmBgYHtyIG5hdmJhclBhZ2V9DQp1aSA8LSBmbHVpZFBhZ2UoDQogICMgUGFuZWwgZGUgdMOtdHVsbw0KICB0aXRsZVBhbmVsKCJUw610dWxvIGRlIGxhIGFwcCIpLA0KICANCiAgbmF2YmFyUGFnZSgiVMOtdHVsbyBkZSBsYSBiYXJyYSIsDQogICAgDQogICAgdGFiUGFuZWwoIlBhbmVsIDEiLA0KICAgICAgICAgICAgIC4uLg0KICAgICAgICAgICAgICksDQogICAgDQogICAgdGFiUGFuZWwoIlBhbmVsIDIiLA0KICAgICAgICAgICAgIC4uLg0KICAgICAgICAgICAgICksDQogICAgDQogICAgdGFiUGFuZWwoIlBhbmVsIDMiLA0KICAgICAgICAgICAgIC4uLg0KICAgICAgICAgICAgICkNCiAgKQ0KKQ0KYGBgDQoNCiFbXShmaWdzL25hdmJhclBhZ2UuUE5HKQ0KDQoNCiMjIyBQYW5lbCBjb24gbGlzdGEgZGUgbmF2ZWdhY2nDs24sIGBuYXZsaXN0UGFuZWxgDQoNClNlIHB1ZWRlIGNyZWFyIHVuYSBsaXN0YSBkZSBuYXZlZ2FjacOzbiwgcXVlIGluY2x1eWEgdmFyaW9zIHBhbmVsZXMgcGFyYSBtb3N0cmFyIGRpc3RpbnRhIGluZm9ybWFjacOzbi4NCg0KYGBge3IgbmF2bGlzdFBhbmVsfQ0KdWkgPC0gZmx1aWRQYWdlKA0KICAjIFBhbmVsIGRlIHTDrXR1bG8NCiAgdGl0bGVQYW5lbCgiVMOtdHVsbyBkZSBsYSBhcHAiKSwNCiAgDQogIG5hdmxpc3RQYW5lbCgNCiAgICANCiAgICB0YWJQYW5lbCgiUGFuZWwgMSIsDQogICAgICAgICAgICAgLi4uDQogICAgICAgICAgICAgKSwNCiAgICANCiAgICB0YWJQYW5lbCgiUGFuZWwgMiIsDQogICAgICAgICAgICAgLi4uDQogICAgICAgICAgICAgKSwNCiAgICANCiAgICB0YWJQYW5lbCgiUGFuZWwgMyIsDQogICAgICAgICAgICAgLi4uDQogICAgICAgICAgICAgKQ0KICApDQopDQpgYGANCg0KIVtdKGZpZ3MvbmF2bGlzdFBhbmVsLlBORykNCg0KIyMjIHstfQ0KDQojIyBGdW5jaW9uZXMgYEhUTUxgDQoNCkxhcyBzaGlueSBhcHBzLCBhbCBpZ3VhbCBxdWUgbG9zIFIgTm90ZWJvb2tzIG8gUiBNYXJrZG93biBmdW5jaW9uYW4gYSBiYXNlIGRlIEhUTUwsIHBvciBsbyBxdWUgc2UgcHVlZGVuIHV0aWxpemFyIGZ1bmNpb25lcyBzaW4gbWF5b3IgcHJvYmxlbWEuDQoNCkFsZ3VuYXMgZGUgZXN0YXMgc29uOg0KDQoqIGBoIygpYCBwYXJhIGRpc3RpbnRvcyBuaXZlbGVzIGRlIGVuY2FiZXphZG9zLg0KDQoqIGBwKClgIHBhcmEgZ2VuZXJhciBww6FycmFmb3MuDQoNCiogYHN0cm9uZygpYCBwYXJhIHBvbmVyIHRleHRvIGVuICoqbmVncml0YXMqKi4NCg0KKiBgZW0oKWAgZ2VuZXJhIHRleHRvIGVuICpjdXJzaXZhKi4NCg0KKiBgY29kZSgpYCBwb25lIHRleHRvIGVuIGZvcm1hdG8gZGUgYGPDs2RpZ29gLg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCnVpIDwtIGZsdWlkUGFnZSgNCiAgICB0aXRsZVBhbmVsKCJUw610dWxvIGRlIGxhIGFwcCIpLA0KICAgIHNpZGViYXJMYXlvdXQoDQogICAgICAgIHNpZGViYXJQYW5lbCgic2lkZWJhciBwYW5lbCIpLA0KICAgICAgICBtYWluUGFuZWwoIm1haW4gcGFuZWwiLA0KICAgICAgICAgICAgICAgICAgaDEoImVuY2FiZXphZG8gbml2ZWwgMSIpLA0KICAgICAgICAgICAgICAgICAgaDIoImVuY2FiZXphZG8gbml2ZWwgMiIpLA0KICAgICAgICAgICAgICAgICAgaDMoImVuY2FiZXphZG8gbml2ZWwgMyIpLA0KICAgICAgICAgICAgICAgICAgcCgiSW5pY2lhIHDDoXJyYWZvIGNvbiB0ZXh0byBub3JtYWwsIiwgDQogICAgICAgICAgICAgICAgICAgIHN0cm9uZygidGV4dG8gZW4gbmVncml0YXMgZGVudHJvIGRlbCBww6FycmFmbywiKSwNCiAgICAgICAgICAgICAgICAgICAgZW0oInRhbWJpw6luIHRleHRvIGVuIGN1cnNpdmEiKSwgInkiLA0KICAgICAgICAgICAgICAgICAgICBjb2RlKCJ0ZXh0byBjb24gZm9ybWF0byBkZSBjw7NkaWdvIikpLA0KICAgICAgICAgICAgICAgICAgcCgiVW4gbnVldm8gcMOhcnJhZm8uIikpDQogICAgKQ0KKQ0KYGBgDQoNCiMjIFRlbWFzDQoNCkxhIHBhcXVldGVyw61hIGBzaGlueXRoZW1lc2AgY3VlbnRhIGNvbiB2YXJpb3MgdGVtYXMgcHJlaW5zdGFsYWRvcyBxdWUgc2UgcHVlZGVuIGFncmVnYXIgZsOhY2lsbWVudGUgYSBsYSBhcHAuIFBhcmEgdXRpbGl6YXJsb3MsIGJhc3RhIGNvbiBtYW5kYXIgbGxhbWFyIGxhIGxpYnJlcsOtYSB5LCBkZW50cm8gZGUgbGEgaW50ZXJmYXogZGUgdXN1YXJpbyBkZWZpbmlyIGB0aGVtZSA9IHNoaW55dGhlbWUoInRlbWEgZXNjb2dpZG8iKWAsIHBhcmEgcXVlIHN1IGFwcCBzZSBtdWVzdHJlIGFzw60uIFB1ZWRlbiB2ZXIgdW5hIGxpc3RhIGRlIGxvcyB0ZW1hcyBzb3BvcnRhZG9zIGFjdHVhbGVzIFthcXXDrV0oaHR0cHM6Ly9ib290c3dhdGNoLmNvbS8zLykuIFBvciBlamVtcGxvOg0KDQoNCmBgYHtyIHNoaW55dGhlbWVzfQ0Kc2hpbnlVSShuYXZiYXJQYWdlKHRpdGxlID0gIkRhdGEgYW5hbHlzaXMgYXBwIiwNCiAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICB0aGVtZSA9IHNoaW55dGhlbWUoInVuaXRlZCIpLA0KICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgIHRhYlBhbmVsKCJTZWF0dGxlIEhvdXNlIHByaWNlcyIsDQogICAgICAgICAgICAgICAgICAgKQ0KKQ0KKQ0KYGBgDQoNCiMjIE3DoXMgcGVyc29uYWxpemFjacOzbiBjb24gSFRNTCB5IENTUw0KDQpTaSBlc3TDoW4gZmFtaWxpYXJpemFkb3MgY29uIEhUTUwgeSBDU1MsIHNlIHB1ZWRlIHBlcnNvbmFsaXphciBjdWFudG8gcXVpZXJhbiBzdSBhcGxpY2FjacOzbiBvIHBhcnRlcyBkZSBlbGxhLg0KDQpQLiBlai4sIHNlIHB1ZWRlIGFncmVnYXIgdW4gYXJjaGl2byBjb24gdG9kYXMgbGFzIGNhcmFjdGVyw61zdGljYXMgQ1NTLiBQYXJhIGVzdG8sIHNlIG5lY2VzaXRhIHBvbmVyIGVuIHVuYSBjYXJwZXRhICgqZGVudHJvIGRlbCBkaXJlY3RvcmlvIGRlIGxhIGFwcCopIHF1ZSBsbGV2ZSBwb3Igbm9tYnJlICIqKnd3dyoqIiB5IHBhcmEgY2FyZ2FyIGVsIHRlbWEsIHNlIGhhcsOtYSBkZSBtYW5lcmEgc2ltaWxhciBxdWUgY29uIGxvcyBgc2hpbnl0aGVtZXNgLiBBc3VtaWVuZG8gcXVlIHN1IGFyY2hpdm8gQ1NTIHNlIGxsYW1hICJib290c3RyYXAuY3NzIiwgc2UgcG9kcsOtYSBjYXJnYXIgYXPDrToNCg0KYGBge3IgY3NzfQ0Kc2hpbnlVSShuYXZiYXJQYWdlKHRpdGxlID0gIkRhdGEgYW5hbHlzaXMgYXBwIiwNCiAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICB0aGVtZSA9ICJib290c3RyYXAuY3NzIiwNCiAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICB0YWJQYW5lbCgiU2VhdHRsZSBIb3VzZSBwcmljZXMiLA0KICAgICAgICAgICAgICAgICAgICkNCikNCikNCmBgYA0KDQojIyBDYXJnYSBkZSBpbcOhZ2VuZXMNCg0KU2UgcHVlZGVuIGluY2x1aXIgaW3DoWdlbmVzIGRlbnRybyBkZSBsYSBhcHAsIGNvbW8gbG9nb3RpcG9zLCBmb25kb3MsIGV0Yy4gTG9zIGFyY2hpdm9zIGRlYmVuIGVzdGFyIGd1YXJkYWRvcyB0YW1iacOpbiBlbiBsYSBjYXJwZXRhICIqKnd3d3cqKiIgcGFyYSBxdWUgKipSKiogcHVlZGEgZW5jb250cmFybG9zLg0KDQpMYSBpbWFnZW4gc2UgcHVlZGUgZGVmaW5pciBkZW50cm8gZGUgY3VhbHF1aWVyIHRpcG8gZGUgaW50ZXJmYXogc2VsZWNjaW9uYWRhIGNvbiBsYSBmdW5jacOzbiBgaW1nKClgLg0KDQpgYGB7ciBpbWd9DQppbWcoc3JjID0gImltYWdlLmpwZyIsIA0KICAgICAgICAgICAgICAgIGhlaWdodCA9ICI3MCUiLCB3aWR0aCA9ICI3MCUiLCBhbGlnbiA9ICJjZW50ZXIiKQ0KYGBgDQoNCg0KIyBPYmpldG9zIGVuIHNoaW55DQoNCkhhc3RhIGFob3JhIHNvbG8gaGVtb3MgdmlzdG8gY8OzbW8gc2UgcHVlZGUgcGVyc29uYWxpemFyIGxhIGFwcC4gU2luIGVtYmFyZ28sIG5vIGhlbW9zIGFncmVnYWRvIG5pbmfDum4gb2JqZXRvIGNvbiBlbCBxdWUgZWwgdXN1YXJpbyBwdWVkYSBpbnRlcmFjdHVhciAoYGlucHV0c2ApIG8gdmlzdWFsaXphciBkZXNwdcOpcyBkZSBzdSBpbnRlcmFjY2nDs24gKGBvdXRwdXRzYCkuDQoNCg0KIyMgRW50cmFkYXMgZGVsIHVzdWFyaW8sIGBpbnB1dHNgDQoNCkV4aXN0ZW4gbXVjaG9zIHRpcG9zIGRlIG9iamV0bywgY3JlYWRvcyBwYXJhIHF1ZSBlbCB1c3VhcmlvIHB1ZWRhIGludGVyYWN0dWFyIGNvbiBsYSBhcHAuIFRvZG9zIGVzdG9zIGBpbnB1dHNgIHNlIGRlZmluZW4gZGVudHJvIGRlIGxhIGludGVyZmF6IGRlIHVzdWFyaW8sIGB1aWAuDQoNCkVudHJlIGVsbG9zOg0KDQoqIEJvdG9uZXMgZGUgYWNjacOzbiwgYGFjdGlvbkJ1dHRvbigpYA0KDQoqIENhc2lsbGFzIGRlIHZlcmlmaWNhY2nDs246DQogIC0gbcO6bHRpcGxlcywgYGNoZWNrYm94R3JvdXBJbnB1dCgpYA0KICAtIHNlbmNpbGxhcywgYGNoZWNrYm94SW5wdXQoKWANCg0KKiBTZWxlY3RvcmVzIGRlIGZlY2hhOg0KICAtIHVuYSBmZWNoYSBlbiBwYXJ0aWN1bGFyLCBgZGF0ZUlucHV0KClgDQogIC0gdW4gcmFuZ28gZGUgZmVjaGFzLCBgZGF0ZVJhbmdlSW5wdXQoKWANCiAgDQoqIENhcmdhIGRlIGFyY2hpdm9zLCBgZmlsZUlucHV0KClgDQoNCiogVmFsb3JlcyBudW3DqXJpY29zLCBgbnVtZXJpY0lucHV0KClgDQoNCiogQm90b25lcyBkZSByYWRpbywgYHJhZGlvQnV0dG9ucygpYA0KDQoqIExpc3RhcyBkZXNwbGVnYWJsZXMsIGBzZWxlY3RJbnB1dCgpYA0KDQoqIEJhcnJhIGRlc2xpemFudGUsIGBzbGlkZXJJbnB1dCgpYA0KDQoqIEJvdMOzbiBkZSBlbnZpYXIsIGBzdWJtaXRCdXR0b24oKWANCg0KKiBFbnRyYWRhcyBkZSB0ZXh0bywgYHRleHRJbnB1dCgpYA0KDQoqIFNlbGVjY2nDs24gZGUgY29sb3JlcywgYGNvbG91cklucHV0KClgICogKmRpc3BvbmlibGUgY29uIGxhIHBhcXVldGVyw61hKiBgY29sb3VycGlja2VyYC4NCg0KQ2FkYSB1bm8gZGUgZXN0b3Mgb2JqZXRvcyBkZSBpbnB1dCBkZWJlIHRlbmVyIHN1IHByb3BpbyBpZGVudGlmaWNhZG9yICoqw7puaWNvKiosIGBpbnB1dElkYCwgcXVlLCBkZSBoZWNobywgZXMgZWwgcHJpbWVyIGFyZ3VtZW50byBkZSBjYWRhIGZ1bmNpw7NuLiBDb24gZXN0ZSBJRCBtYW5kYXJlbW9zIGxsYW1hciBsYXMgZW50cmFkYXMgZGVsIHVzdWFyaW8gcGFyYSBtb2RpZmljYXIgbG9zIHJlc3VsdGFkb3MgZW4gbGEgcGFydGUgZGVsIHNlcnZpZG9yIChgc2VydmVyYCkuDQoNCiMjIFJlc3B1ZXN0YSBkZSBsYSBhcHAsIGBvdXRwdXRzYA0KDQpQYXJhIHBvZGVyIG1vc3RyYXIgZ3LDoWZpY2FzLCB0YWJsYXMsIHRleHRvLCBldGMuIHVuYSB2ZXogcXVlIGVsIHVzdWFyaW8gaW50ZXJhY3R1w7MgY29uIGxvcyBgaW5wdXRzYCwgZXMgbmVjZXNhcmlvIGRlZmluaXIgKHRvZGF2w61hIGRlbnRybyBkZSBsYSBgdWlgKSBlbCAgdGlwbyBkZSBgb3V0cHV0YCBxdWUgcXVlcmVtb3MgbW9zdHJhci4NCg0KRXN0YXMgZnVuY2lvbmVzIGRlIGBvdXRwdXRgIHZhbiBhY29tcGHDsWFkYXMgZGUgdW5hIGZ1bmNpw7NuIHNpbWlsYXIgZW4gbGEgcGFydGUgZGVsIHNlcnZpZG9yLiBEZWJham8gc2UgbXVlc3RyYW4gdmFyaWFzIGZ1bmNpb25lcyBjb211bmVzLCBqdW50byBjb24gc3UgcGFyIHBhcmEgbGEgcGFydGUgZGVsIHNlcnZpZG9yLg0KDQoNCnwgRnVuY2nDs24gZGUgbGEgYHVpYCB8IERlc2NyaXBjacOzbiB8IEZ1bmNpw7NuIGRlbCBgc2VydmVyYCAgfA0KfDotLS0tLS0tLS0tLS0tLS0tLS06fDotLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLTp8DQp8IGBwbG90T3V0cHV0KClgfCBHcsOhZmljYSBlc3TDoXRpY2EgfCBgcmVuZGVyUGxvdCgpYCAgICAgICAgfA0KfCBgcGxvdGx5T3V0cHV0KClgfEdyw6FmaWNhIGludGVyYWN0aXZhIHwgYHJlbmRlclBsb3RseSgpYCAgfA0KfCBgdGFibGVPdXRwdXQoKWAgfCBUYWJsYSBkZSBkYXRvcyB8IGByZW5kZXJUYWJsZSgpYCAgICAgICB8DQp8IGBkYXRhVGFibGVPdXRwdXQoKWB8VGFibGEgaW50ZXJhY3RpdmF8YHJlbmRlckRhdGFUYWJsZSgpYHwNCnwgYHRleHRPdXRwdXQoKWAgfCBUZXh0byBjb24gZm9ybWF0byB8IGByZW5kZXJUZXh0KClgICAgICAgfA0KfCBgdmVyYmF0aW1UZXh0T3V0cHV0KClgfFRleHRvIGRlIGNvbnNvbGEgfCBgcmVuZGVyUHJpbnQoKWB8DQp8IGBpbWFnZU91dHB1dCgpYCAgICB8IEltYWdlbiAgICAgIHwgYHJlbmRlckltYWdlKClgICAgICAgIHwNCg0KDQojIENvbmZpZ3VyYWNpw7NuIGRlbCBzZXJ2aWRvciwgYHNlcnZlcmANCg0KDQoNCg0KDQoNCg0K